# message.rb
# Copyright (C) 2004-2006 Akira TAGOH

# Authors:
#   Akira TAGOH  <at@gclab.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

require 'singleton'
require 'prune/debug'
require 'prune/pattern'
require 'prune/error'


module PRUNE

=begin rdoc

== PRUNE::MessageHandler

=end

  class MessageHandler
    include Singleton
    include PRUNE::Debug

=begin rdoc

=== PRUNE::MessageHandler#new(msg)

=end

    def new(msg)
      command = msg[:command].upcase
      if command =~ /\A\d+/ then
        command = PRUNE::Message.get_responsename(command)
        PRUNE.Fail(PRUNE::Error::InvalidMessage, debug_sprintf("%s", msg)) if command.nil?
      else
        PRUNE.Fail(PRUNE::Error::InvalidMessage, debug_sprintf("%s", msg)) unless PRUNE::Message.valid_command?(command)
      end
      retval = eval("PRUNE::Message::#{command}.new(msg)")

      return retval
    end # def new

  end # class MessageHandler

=begin rdoc

== PRUNE::Message

=end

  module Message

    ChannelLocationInfo = Struct.new('ChannelLocationInfo', :location, :index)
    CH_PARAM = 1
    CH_TRAILING = 2

    @@ChannelLocationMap = {}
    @@ResponseMap = {}
    @@ReverseResponseMap = {}
    @@CommandResponseMap = {}

    %w(JOIN,KICK,MODE,NAMES,NOTICE,PART,PRIVMSG,TOPIC,WHO|CH_PARAM,0
       INVITE,315,322,324,329,332,333,352,366,367,368,403,404,405,437,442,467,471,473,474,475,476,477,478,482|CH_PARAM,1
       341,353,441,443|CH_PARAM,2
    ).each do |line|
      commands, info = line.split('|')
      loc, idx = info.split(',')
      commands.split(',').each do |cmd|
        @@ChannelLocationMap[cmd] = ChannelLocationInfo.new(eval(loc), idx.to_i)
      end
    end
    PRUNE::PATTERN::Responses.each do |line|
      num, symbol = line.split(',', 2)
      @@ResponseMap[symbol] = num
      @@ResponseMap[symbol.to_sym] = num
      @@ReverseResponseMap[num] = symbol.to_sym
    end
    PRUNE::PATTERN::ResponsesMap.each do |line|
      cmd, *resp = line.split(',')
      @@CommandResponseMap[cmd] = resp
    end

    class << self
      def get_chinfo(cmd)
        return @@ChannelLocationMap[cmd.upcase]
      end # def get_chinfo

      def valid_command?(cmd)
        return PRUNE::PATTERN::Commands.map {|n| n.split('|')}.flatten.include?(cmd.upcase)
      end # def validate_command?

      def get_responsename(num)
        return @@ReverseResponseMap[num]
      end # def get_responsename
    end

=begin rdoc

=== PRUNE::Message::Core

=end

    class Core

=begin rdoc

==== PRUNE::Message::Core#new(msg)

=end

      def initialize(msg)
        if !msg.kind_of?(Hash) ||
            !msg.include?(:prefix) || !msg.include?(:command) ||
            !msg.include?(:params) || !msg[:params].kind_of?(Array) then
          PRUNE.Fail(PRUNE::Error::InvalidMessage, msg.inspect)
        end
        @message = msg
        @must = false
      end # def initialize

=begin rdoc

==== PRUNE::Message::Core#must=(flag)

=end

      def must=(flag)
	@must = (flag == true ? flag : false)
      end # def must=

=begin rdoc

==== PRUNE::Message::Core#is_must?

=end

      def is_must?
	return @must
      end # def is_must?

=begin rdoc

==== PRUNE::Message::Core#nick

=end

      def nick
        return @message[:prefix][:nick]
      end # def nick

=begin rdoc

==== PRUNE::Message::Core#nick=(new)

=end

      def nick=(new)
        @message[:prefix][:nick] = new
      end # def nick=

=begin rdoc

==== PRUNE::Message::Core#user(noident = false)

=end

      def user(noident = false)
        retval = nil

        if noident then
          retval = @message[:prefix][:user].sub(/\A~/, '') unless @message[:prefix][:user].nil?
        else
          retval = @message[:prefix][:user]
        end

        return retval
      end # def user

=begin rdoc

==== PRUNE::Message::Core#user=(new)

=end

      def user=(new)
        @message[:prefix][:user] = new
      end # def user=

=begin rdoc

==== PRUNE::Message::Core#host

=end

      def host
        return @message[:prefix][:host]
      end # def host

=begin rdoc

==== PRUNE::Message::Core#host=(new)

=end

      def host=(new)
        @message[:prefix][:host] = new
      end # def host=

=begin rdoc

==== PRUNE::Message::Core#time

=end

      def time
        return @message[:time]
      end # def time

=begin rdoc

==== PRUNE::Message::Core#command

=end

      def command
        return @message[:command]
      end # def command

=begin rdoc

==== PRUNE::Message::Core#params(num = nil)

=end

      def params(num = nil)
        if num.nil? then
          return @message[:params]
        else
          raise ArgumentError, sprintf("Must be Integer, %s", num.class) unless num.kind_of?(Integer)
          return @message[:params][num]
        end
      end # def params

=begin rdoc

==== PRUNE::Message::Core#channel(cmd, opt = {'suffix'=>false, 'array'=>false})

=end

      def channel(cmd = nil, opt = {'suffix'=>false, 'array'=>false})
        raise RuntimeError, "No real method of channel" if cmd.nil?

        retval = []
        map = PRUNE::Message.get_chinfo(cmd)
        unless map.nil? then
          case map.location
          when CH_PARAM
            retval = [self.params(map.index)]
          when CH_TRAILING
            retval = [self.params(-1)]
          end
          if opt['suffix'] == true then
            (0..retval.length-1).each do |i|
              retval[i] = sprintf("%s%s", retval[i], @message[:suffix])
            end
          end
        end # unless map.nil?

        retval = retval[0] if !opt.has_key?('array') || opt['array'] == false
        return retval
      end # def channel

=begin rdoc

==== PRUNE::Message::Core#to_s(cmd)

=end

      def to_s(cmd = nil)
        raise RuntimeError, "No real method of to_s" if cmd.nil?

        retval = ""
        map = PRUNE::Message.get_chinfo(cmd)
        msg = Marshal.load(Marshal.dump(@message))
        msg[:suffix] = '' if msg[:suffix].nil?
        unless map.nil? then
          index = -1
          case map.location
          when CH_PARAM
            index = map.index
          when CH_TRAILING
            index = -1
          end
          msg[:params][index] = sprintf("%s%s", msg[:params][index], msg[:suffix])
        end
        retval << ":" if !self.nick.nil? || !self.host.nil?
        if !msg[:suffix].nil? && !msg[:suffix].empty? &&
            !self.channel.nil? && self.channel !~ /#{PRUNE::PATTERN::CHANNEL}/ then
          retval << sprintf("%s%s", self.nick, msg[:suffix]) if !self.nick.nil? && !self.user.nil?
        else
          retval << sprintf("%s", self.nick) if !self.nick.nil? && !self.user.nil?
        end
        retval << sprintf("!%s@", self.user) unless self.user.nil?
        retval << sprintf("%s", self.host) unless self.host.nil?
        retval << " " if retval.length > 1
        retval << sprintf("%s", cmd.upcase)
        retval << " " if !self.params.empty?
        msg[:params][0..-2].each do |p|
          retval << sprintf("%s ", p)
        end
        retval << sprintf(":%s", msg[:params][-1]) if !msg[:params][-1].nil? && !msg[:params][-1].empty?
        retval << "\r\n"

        return retval
      end # def to_s

=begin rdoc

==== PRUNE::Message::Core#suffix

=end

      def suffix
        return @message[:suffix]
      end # def suffix

=begin rdoc

==== PRUNE::Message::Core#suffix=(new)

=end

      def suffix=(new)
        @message[:suffix] = new
      end # def suffix=

=begin rdoc

==== PRUNE::Message::Core#==(obj)

=end

      def ==(obj)
        return false unless obj.kind_of?(PRUNE::Message::Core)

        return self.to_s == obj.to_s
      end # def ==

    end # class Core

    PRUNE::PATTERN::Commands.map {|n| n.split('|')}.flatten.each do |cmd|
      begin
        module_eval <<-EOS, __FILE__, __LINE__+1
        class #{cmd.upcase} < PRUNE::Message::Core
          def initialize(arg, *args)
            msg = nil
            unless arg.kind_of?(Hash) then
              msg = {:prefix=>{:nick=>nil,
                               :user=>nil,
                               :host=>nil},
                     :command=>"#{cmd.upcase}",
                     :params=>args.unshift(arg),
                     :time=>Time.now,
              }
            else
              msg = arg
              msg[:command] = '#{cmd.upcase}' unless msg.include?(:command)
            end
            super(msg)
          end
          def channel(opt = {'suffix'=>false, 'array'=>false})
            super("#{cmd.upcase}", opt)
          end # def channel
          def to_s
            retval = super("#{cmd.upcase}")
            return retval
          end # def to_s
          alias :inspect :to_s
        end
        EOS
      rescue SyntaxError
        raise NameError, sprintf("invalid identifier %s%s", cmd, caller(3))
      end
    end
    @@ResponseMap.each do |key,val|
      if key.kind_of?(Symbol) then
        begin
          module_eval <<-EOS, __FILE__, __LINE__+1
          class #{key} < PRUNE::Message::Core
            def initialize(arg, *args)
              msg = nil
              unless arg.kind_of?(Hash) then
                msg = {:prefix=>{:nick=>nil,
                                 :user=>nil,
                                 :host=>nil},
                       :command=>"#{val}",
                       :params=>args.unshift(arg),
                       :time=>Time.now,
                }
              else
                msg = arg
                msg[:command] = '#{val}' unless msg.include?(:command)
              end
              super(msg)
            end
            def channel(opt = {'suffix'=>false, 'array'=>false})
              super("#{val}", opt)
            end # def channel
            def to_s
              retval = super("#{val}")
              return retval
            end # def to_s
            alias :inspect :to_s
            def to_sym
              return #{key}
            end # def to_sym
            def to_num
              return #{val}
            end
          end
          EOS
        rescue SyntaxError
          raise NameError, sprintf("invalid identifier %s\n%s", val, caller(3))
        end
      end
    end

  end # module Message

end # module PRUNE
